

#include "bulletinboard.h"

#include <errno.h>
#include <netdb.h>
#include <poll.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>

#include <iostream>
#include <set>
#include <sstream>

#include <climits>
#include <cstdio>


using namespace std;

// Some constants.
const int defaultPort = 8000;
const int monitorInterval = 10;
const int threadIncrement = 1;
const int maxThreads = 4;

// Our shared BulletinBoard instance that will do all the actual "work".
static BulletinBoard bb;

// A shared structure used to store info about the number of threads.
struct ThreadData
{
    int totalThreads;
    int activeThreads;
    pthread_mutex_t mutex;
} td;


// A convenience method to convert strings to integers.
int intFromString( const string & s )
{
    istringstream iss( s );
    int i;
    if ( !(iss >> i) )
        return INT_MIN;
    return i;
}


// The function run in the threads. It handles client connections,
// interactions and disconnections.
void * clientHandler( int masterSocket )
{
    while ( true )
    {
        // Poll the master socket for connection requests.
        struct pollfd pollData;
        pollData.fd = masterSocket;
        pollData.events = POLLIN;
        int numReady = poll( &pollData, 1, monitorInterval );

        // If the poll timed out, we check to see to if this thread is still
        // needed and exit if not.
        if ( numReady == 0 )
        {
            pthread_mutex_lock( &td.mutex );
            if ( td.totalThreads > threadIncrement
                 && td.totalThreads - td.activeThreads > threadIncrement + 1 )
            {
                --(td.totalThreads);
                pthread_mutex_unlock( &td.mutex );
                printf( "Thread %X shutting down.\n", pthread_self() );
                pthread_exit( 0 );
            }
            pthread_mutex_unlock( &td.mutex );
        }

        // Accept the connection and get the socket.
        struct sockaddr_in sin;
        socklen_t length = sizeof( sin );
        int socket = accept( masterSocket, (struct sockaddr *)( &sin ), &length );
        if ( socket < 0 )
        {
            if ( errno != EINTR )
                perror("New client accept");
            continue;
        }
        printf( "New client in thread %X.\n", pthread_self() );

        // Increment the active thread count.
        pthread_mutex_lock( &td.mutex );
        ++(td.activeThreads);
        pthread_mutex_unlock( &td.mutex );

        // Initialise the username to the default "nobody".
        string user = "nobody";

        bool done = false;
        while ( !done )
        {
            string line;

            // Read characters from the socket one by one.
            while ( true )
            {
                char c;
                int bytesRead = read( socket, &c, 1 );

                // On read errors we drop the client. I'm not sure this is the
                // best course of action, but it is the easiest.
                if ( bytesRead == -1 )
                {
                    perror("Read from slave socket");
                    done = true;
                    break;
                }

                // Zero bytes read means the client closed the connection, so
                // move on.
                if ( bytesRead == 0 )
                {
                    string reply = "BYE It's rude to leave without saying goodbye.";
                    send( socket, reply.c_str(), reply.size(), 0 );
                    done = true;
                    break;
                }

                // Once we see a new line, send the line we;ve read to be
                // interpretted.
                if ( c == '\n')
                {
                    string reply = bb.interpretCommand( line, user, done );
                    send( socket, reply.c_str(), reply.size(), 0 );
                    break;
                }

                line += c;
            }
        }

        // We're down with this client, so we shutdown and close the socket.
        shutdown( socket, SHUT_RDWR );
        close( socket );

        // Decrement the active thread count.
        pthread_mutex_lock( &td.mutex );
        --(td.activeThreads);
        pthread_mutex_unlock( &td.mutex );
    }

    return 0;
}

int main ( int argc, char * * argv )
{
    // Check that a filename has been passed and give it to the BulletinBoard.
    if ( argc < 2 )
    {
        printf( "The first argument to bbserv must by the path to the database." );
        return 1;
    }
    bb.setFileName( argv[1] );

    // Check for and validate a port argument, falling back on the default if
    // needed.
    int port = argc < 3 ? defaultPort : intFromString( argv[2] );
    if ( port < 1024 || port > 65535 )
    {
        printf( "Invalid port number." );
        return 1;
    }    

    // Create the master socket.
    int masterSocket = socket( PF_INET, SOCK_STREAM, 0 );
    if ( masterSocket < 0 )
    {
        perror( "Master socket creation" );
        return 1;
    }

    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = htonl( INADDR_ANY );
    sin.sin_port = htons( port );

    // Bind the socket and prepare if for incoming connection requests.
    if ( bind( masterSocket, (struct sockaddr *)( &sin ), sizeof( sin ) ) < 0 )
    {
        close( masterSocket );
        perror( "Master socket binding" );
        return 1;
    }
    if ( listen( masterSocket, 16 ) < 0 )
    {
        close( masterSocket );
        perror( "Master socket listening" );
        return 1;
    }

    // We'll use the same attributes for all our threads.
    pthread_attr_t threadAttributes;
    pthread_attr_init( &threadAttributes );
    pthread_attr_setdetachstate( &threadAttributes, PTHREAD_CREATE_DETACHED );

    // Initialise the thread counts to zero.
    td.totalThreads = 0;
    td.activeThreads = 0;

    while( true )
    {
        // Check if more threads are needed. If so, create them.
        pthread_mutex_lock( &td.mutex );
        bool needThreads = td.activeThreads >= td.totalThreads
                           && td.totalThreads < maxThreads;
        pthread_mutex_unlock( &td.mutex );
        if ( needThreads )
        {
            pthread_t threadId;
            for ( int i = 0; i < threadIncrement; ++i )
            {
                int error = pthread_create( &threadId,
                                            &threadAttributes,
                                            (void * (*) (void *))( clientHandler ),
                                            (void *)( masterSocket ) );
                if ( error )
                {
                    perror( "ThreadCreation" );
                }
                else
                {
                    printf( "Created new thread: %X\n", threadId );
                    pthread_mutex_lock( &td.mutex );
                    ++(td.totalThreads);
                    pthread_mutex_unlock( &td.mutex );
                }
            }
        }

        // Sleep until the next check.
        sleep( monitorInterval );
    }

    return 0;
}